package com.holic.spring.custom;

import com.holic.spring.custom.annotation.GraphQLComponent;
import com.holic.spring.custom.annotation.GraphQLMapping;
import com.holic.spring.custom.config.GraphQLSource;
import com.holic.spring.custom.exception.CustomDataFetcherExceptionHandler;
import com.holic.spring.custom.fetcher.DynamicMethodDataFetcher;
import com.holic.spring.custom.type.CustomScalars;
import graphql.GraphQL;
import graphql.com.google.common.collect.Lists;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.RuntimeWiring;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.ReflectionUtils;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @ClassName GraphQLBeanPostProcessor
 * @Description
 * @Author Neil
 * @Date 2024/11/12 13:43
 * @Version V1.0
 **/
@Component
public class GraphQLBeanPostProcessor implements ApplicationListener<ContextRefreshedEvent> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    GraphQLSource graphQLSource;
    @Autowired
    GraphQLMappingDefinitionRegistry definitionRegistry;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        ApplicationContext applicationContext = event.getApplicationContext();
        Map<String, Object> serviceMap = applicationContext.getBeansWithAnnotation(GraphQLComponent.class);
        serviceMap.forEach((beanName, bean) -> {
            ReflectionUtils.doWithMethods(bean.getClass(), method -> {
                GraphQLMapping graphQLMapping = method.getAnnotation(GraphQLMapping.class);
                if(graphQLMapping != null){
                    GraphQLMappingDefinition definition = new GraphQLMappingDefinition(bean, method, graphQLMapping);
                    definitionRegistry.registerDefinition(definition);
                }
            });
        });
        try {
            bindGraphQLMappings();
        }catch(IOException ex){
            throw new RuntimeException(ex);
        }
    }

    private void bindGraphQLMappings() throws IOException {
        ClassPathResource resource = new ClassPathResource("graphql/");
        TypeDefinitionRegistry typeRegistry = new TypeDefinitionRegistry();
        Arrays.stream(resource.getFile().listFiles((dir, name) -> name.endsWith(".graphqls"))).forEach(file -> {
            TypeDefinitionRegistry fragmentRegistry = new SchemaParser().parse(file);
            typeRegistry.merge(fragmentRegistry);
        });
        RuntimeWiring wiring = buildWiring();
        GraphQLSchema schema = new SchemaGenerator().makeExecutableSchema(typeRegistry, wiring);
        GraphQL graphQL = GraphQL.newGraphQL(schema)
                .defaultDataFetcherExceptionHandler(new CustomDataFetcherExceptionHandler())
                .build();
        graphQLSource.setGraphQL(graphQL);
    }

    private RuntimeWiring buildWiring() {
        RuntimeWiring.Builder runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring();
        definitionRegistry.doWithEachDefinition(definition -> {
            runtimeWiringBuilder.type(definition.getType(), builder -> builder.dataFetcher(definition.getName(), new DynamicMethodDataFetcher(definition)));
            logger.info("Register GraphQLMapping: [ " + definition.getType() + " : " + definition.getBean().getClass().getName() + " : "  + definition.getName() + " ]");
            return null;
        });
        return runtimeWiringBuilder.scalar(CustomScalars.Date).build();
    }

}
